<template>
  <div>
    <slot name="uploadArea" :show-upload="showUpload">
      <el-button
        class="file-upload-button"
        type="text"
        :loading="loading"
        size="mini"
        @click="showUpload"
      >
        <template v-if="!loading">
          {{ buttonText }}
        </template>
        <template v-else>
          上传中...
        </template>
      </el-button>
      <slot name="tip" />
    </slot>
    <el-upload
      v-if="uploadComponentVisible"
      v-show="false"
      ref="fileUpload"
      :accept="accept"
      :multiple="multiple"
      action="void"
      :auto-upload="autoUpload"
      :limit="limit"
      :show-file-list="showFileList"
    />
  </div>
</template>

<script>
import axios from 'axios'
import { getFileExtension } from '@/utils'
import { getToken } from '@/utils/auth'
import { baseURL } from '@/utils/request'

export default {
  name: 'FileUpload',
  props: {
    // 最多文件上传个数
    limit: {
      type: Number,
      default: () => 1
    },
    // 绑定的值
    // eslint-disable-next-line vue/require-default-prop
    value: [Array, Object],
    // 是否可多选
    multiple: {
      type: Boolean,
      default: () => false
    },
    // 是否展示文件上传的列表
    showFileList: {
      type: Boolean,
      default: () => false
    },
    // 是否自动上传
    autoUpload: {
      type: Boolean,
      default: () => false
    },
    // 继续选择文件上传后，是追加还是替换 true-替换 false-追加
    isReplace: {
      type: Boolean,
      default: () => true
    },
    // 超出文件个数的自定义提示
    exceedMsg: {
      type: String,
      default() {
        return `最多上传${this.limit}个附件`
      }
    },
    // 上传文件地址
    uploadUrl: {
      type: String,
      default: () => `/api/v1/brand/logo/upload`
    },
    // 上传文件按钮的文字
    buttonText: {
      type: String,
      default: () => '上传'
    },
    // 上传文件的格式限制
    accept: {
      type: String,
      default: () => '.jpg, .png, .jpeg, .gif, .webp'
    },
    // 上传文件携带的其他参数
    uploadParams: {
      type: Object,
      default: () => ({})
    },
    // 上传前的函数判断
    // eslint-disable-next-line vue/require-default-prop
    beforeFileUpload: {
      type: Function
    },
    // 文件上传成功的回调
    // eslint-disable-next-line vue/require-default-prop
    fileUploaded: {
      type: Function
    }
  },
  data() {
    return {
      tempIndex: 1,
      uploadComponentVisible: false,
      progressStatus: [],
      resultList: [],
      loading: false,
      statusMap: {
        pending: {
          code: 1999,
          msg: '等待上传'
        },
        parsing: {
          code: 2999,
          msg: '解析中'
        },
        uploading: {
          code: 3999,
          msg: '上传中'
        },
        fail: {
          code: 4999,
          msg: '上传失败'
        },
        success: {
          code: 5999,
          msg: '上传成功'
        }
      }
    }
  },
  computed: {

  },
  watch: {
  },
  methods: {
    /**
     * 展示文件选择面板
     */
    showUpload() {
      if (this.loading) return
      this.uploadComponentVisible = false
      this.$nextTick(() => {
        this.uploadComponentVisible = true
        this.$nextTick((_) => {
          this.$refs.fileUpload.clearFiles()
          this.$refs.fileUpload.$children[0].handleClick()
          const change = this.$refs.fileUpload.$children[0].handleChange
          this.$refs.fileUpload.$children[0].handleChange = (ev) => {
            this.modifyChange(ev, change)
          }
        })
      })
    },
    /**
     * 文件选择后的改变
     * @param ev
     * @param change
     */
    modifyChange(ev, change) {
      const { files } = ev.target

      let index = files.length - 1
      const rightFiles = [] // 符合条件的文件

      // 自动过滤不符合格式的文件，不符合尺寸的进行提示
      while (index >= 0) {
        if (files[index]) {
          const rawFile = files[index]
          rawFile.uid = Date.now() + this.tempIndex++
          const file = {
            status: 'ready',
            name: rawFile.name,
            size: rawFile.size,
            percentage: 0,
            uid: rawFile.uid,
            raw: rawFile
          }
          --index

          if (this.accept.includes(getFileExtension(rawFile.name).replace('.', '')) || !this.accept) {
            rightFiles.push(file)
          }
        }
      }
      this.tempIndex = 1
      if (!rightFiles.length) {
        this.$message.warning('未发现可以上传的文件')
        this.reset()
        return
      }
      const vLength = !this.isReplace ? this.value.length : 0
      if ((rightFiles.length + vLength) > this.limit) {
        this.$message.warning(this.exceedMsg)
        this.reset()
        return
      }

      this.submitUpload(rightFiles)
      change.bind(this.$refs.fileUpload.$children[0])(ev)
    },
    /**
     *
     * 执行上传
     */
    async submitUpload(fileList = []) {
      // 上传前的校验,返回reject就停止
      if (this.isFunction(this.beforeFileUpload)) {
        const res = await this.beforeFileUpload(fileList)
        // 没什么东西返回就return
        if (!res) {
          this.reset()
          return
        }
        if (Array.isArray(res) && res.length) {
          // 返回了过滤后的文件, 那就接着传
          // 这里没判断太严谨，一定是文件列表才行，用的时候需要注意，想停止最好是直接返回一个false
          fileList = res
        } else {
          this.reset()
          return
        }
      }
      this.upload(fileList)
    },
    /**
     * 上传成功的回调
     */
    callback(res = []) {
      let result = null
      // 绑定的类型是类型就返回什么类型
      if (Array.isArray(this.value)) {
        result = this.isReplace ? res : [...this.value, ...res]
      } else {
        result = res[0]
      }
      if (this.isFunction(this.fileUploaded)) this.fileUploaded(result)
      this.$emit('input', result)
      this.$emit('upload-success', result)
      this.reset()
    },
    /**
     *
     * 重置上传组件
     */
    reset() {
      if (this.$refs.fileUpload) {
        this.$nextTick(() => {
          this.$refs.fileUpload.clearFiles()
          this.uploadComponentVisible = false
        })
      }
    },
    /**
     * 判断是否是函数
     */
    isFunction(func) {
      return func && typeof func === 'function'
    },

    /**
     * 执行上传操作
     * @returns {Promise<void>}
     */
    async upload(fileList) {
      const index = 0
      this.loading = true
      fileList.forEach((item, idx) => {
        this.$set(this.progressStatus, idx, {
          ...this.statusMap.pending,
          progress: 0,
          uid: item.uid
        })
      })

      await this.send(fileList, index)
      this.callback(this.resultList)
      this.loading = false
      this.progressStatus = []
      this.resultList = []
    },
    /**
     * 发送网络请求
     * @param fileList
     * @param index
     * @returns {Promise<void>}
     */
    async send(fileList, index) {
      const upload = async(fileList, index) => {
        if (fileList.length <= index) return
        this.setStatus(index, {
          progress: 0,
          ...this.statusMap.parsing
        })
        this.setStatus(index, {
          progress: 0,
          ...this.statusMap.uploading
        })
        let result = {}
        const res = await this.uploadReq(fileList[index].raw, index)
        if (res.data.code !== 200) {
          this.setStatus(index, {
            progress: 0,
            ...this.statusMap.fail
          })
          result = {
            ...this.statusMap.fail
          }
        } else {
          result = res.data.data
        }
        this.setStatus(index, {
          progress: 100,
          ...this.statusMap.success
        })
        this.resultList.push(
          {
            ...this.statusMap.success,
            result,
            file: fileList[index]
          }
        )
        index++
        await upload(fileList, index)
      }
      await upload(fileList, index)
    },
    /**
     * 上传网络请求
     * @param file 文件对象
     * @param index 索引
     * @returns {AxiosPromise}
     */
    uploadReq(file, index) {
      const params = new FormData()
      params.append('file', file)
      Object.keys(this.uploadParams).forEach(key => {
        params.append(key, this.uploadParams[key])
      })
      return axios({
        url: `${baseURL}${this.uploadUrl}`,
        method: 'post',
        data: params,
        headers: { 'Content-Type': 'multipart/form-data', token: getToken() },
        timeout: 0,
        onUploadProgress: pe => {
          // 进度条
          this.$nextTick(() => {
            this.progressStatus[index].progress = ((pe.loaded / pe.total) * 100) | 0
          })
        }
      })
    },

    /**
     * 设置状态
     * @param index
     * @param data
     */
    setStatus(index, data) {
      Object.keys(data).forEach(key => {
        if (data[key]) this.progressStatus[index][key] = data[key]
      })
    }
  }
}
</script>

<style scoped lang="scss">
.file-upload-button {
  //padding-bottom: 0;
  //padding: 0;
  .button-icon {
    font-size: 20px;
    vertical-align: middle;
  }
}
</style>
